/*global L: true */

L.KML = L.FeatureGroup.extend({
    options: {
        async: true
    },

    initialize: function (kml, options) {
        L.Util.setOptions(this, options);
        this._kml = kml;
        this._layers = {};
        this._xml = undefined;

        if (kml) {
            this.addKML(kml, options, this.options.async);
        }
    },

    loadXML: function (url, cb, options, async) {
        if (async == undefined) async = this.options.async;
        if (options == undefined) options = this.options;
        var _this = this;
        var req = new window.XMLHttpRequest();
        req.open('GET', url, async);
        try {
            req.overrideMimeType('text/xml'); // unsupported by IE
        } catch (e) { }
        req.onreadystatechange = function () {
            if (req.readyState != 4) {
                //this.fire("error");
                return
            };
            if (req.status == 200)
                cb(req.responseXML, options);
            else
                this.fire("error");
        };
        req.send(null);
    },

    addKML: function (url, options, async) {
        var _this = this;
        var cb = function (gpx, options) { _this._addKML(gpx, options) };
        this.loadXML(url, cb, options, async);
    },

    _addKML: function (xml, options) {
        if (!xml) {
            this.fire("error");
            return;
        }
        this._xml = xml;
        var layers = L.KML.parseKML(xml);
        if (!layers || !layers.length) return;
        for (var i = 0; i < layers.length; i++) {
            this.fire('addlayer', {
                layer: layers[i]
            });
            this.addLayer(layers[i]);
        }
        this.latLngs = L.KML.getLatLngs(xml);
        this.fire("loaded");
    },

    latLngs: []
});

L.Util.extend(L.KML, {

    parseKML: function (xml) {
        var schema = this.parseSchema(xml);
        var style = this.parseStyle(xml);
        var el = xml.getElementsByTagName("Folder");
        var layers = [], l;
        for (var i = 0; i < el.length; i++) {
            if (!this._check_folder(el[i])) { continue; }
            l = this.parseFolder(el[i], style, schema);
            if (l) { layers.push(l); }
        }
        el = xml.getElementsByTagName('Placemark');
        for (var j = 0; j < el.length; j++) {
            if (!this._check_folder(el[j])) { continue; }
            l = this.parsePlacemark(el[j], xml, style, schema);
            if (l) { layers.push(l); }
        }
        return layers;
    },

    // Return false if e's first parent Folder is not [folder]
    // - returns true if no parent Folders
    _check_folder: function (e, folder) {
        e = e.parentElement;
        while (e && e.tagName !== "Folder") {
            e = e.parentElement;
        }
        return !e || e === folder;
    },

    parseSchema: function (xml) {
        var sc = xml.getElementsByTagName("Schema");
        var schemas = {};
        for (var i = 0; i < sc.length; i++) {
            var key = sc[i].attributes["id"].value
            schemas[key] = {}

            for (var nn = 0; nn < sc[i].getElementsByTagName("SimpleField").length; nn++) {
                var field = sc[i].getElementsByTagName("SimpleField")[nn].attributes["name"].value;
                var dataType = sc[i].getElementsByTagName("SimpleField")[nn].attributes["type"].value;

                schemas[key][field] = dataType;
            }
        }

        return schemas;
    },
    parseStyle: function (xml) {
        var style = {};
        var sl = xml.getElementsByTagName("Style");

        //for (var i = 0; i < sl.length; i++) {
        var attributes = {
            color: true, width: true, Icon: true, href: true,
            hotSpot: true
        };

        function _parse(xml) {
            var options = {};
            for (var i = 0; i < xml.childNodes.length; i++) {
                var e = xml.childNodes[i];
                var key = e.tagName;
                if (!attributes[key]) { continue; }
                if (key === 'hotSpot') {
                    for (var j = 0; j < e.attributes.length; j++) {
                        options[e.attributes[j].name] = e.attributes[j].nodeValue;
                    }
                } else {
                    var value = e.childNodes[0].nodeValue;
                    if (key === 'color') {
                        options.opacity = parseInt(value.substring(0, 2), 16) / 255.0;
                        options.color = "#" + value.substring(6, 8) + value.substring(4, 6) + value.substring(2, 4);
                    } else if (key === 'width') {
                        options.weight = value;
                    } else if (key === 'Icon') {
                        ioptions = _parse(e);
                        if (ioptions.href) { options.href = ioptions.href; }
                    } else if (key === 'href') {
                        options.href = value;
                    }
                }
            }
            return options;
        }

        for (var i = 0; i < sl.length; i++) {
            var e = sl[i], el;
            var options = {}, poptions = {}, ioptions = {};
            el = e.getElementsByTagName("LineStyle");
            if (el && el[0]) { options = _parse(el[0]); }
            el = e.getElementsByTagName("PolyStyle");
            if (el && el[0]) { poptions = _parse(el[0]); }
            if (poptions.color) { options.fillColor = poptions.color; }
            if (poptions.opacity) { options.fillOpacity = poptions.opacity; }
            el = e.getElementsByTagName("IconStyle");
            if (el && el[0]) { ioptions = _parse(el[0]); }
            if (ioptions.href) {
                // save anchor info until the image is loaded
                options.icon = new L.KMLIcon({
                    iconUrl: ioptions.href,
                    shadowUrl: null,
                    iconAnchorRef: { x: ioptions.x, y: ioptions.y },
                    iconAnchorType: { x: ioptions.xunits, y: ioptions.yunits }
                });
            }
            style['#' + e.getAttribute('id')] = options;
        }
        return style;
    },

    parseFolder: function (xml, style, schema) {
        var el, layers = [], l;
        el = xml.getElementsByTagName('Folder');
        for (var i = 0; i < el.length; i++) {
            if (!this._check_folder(el[i], xml)) { continue; }
            l = this.parseFolder(el[i], style, schema);
            if (l) { layers.push(l); }
        }
        el = xml.getElementsByTagName('Placemark');
        for (var j = 0; j < el.length; j++) {
            if (!this._check_folder(el[j], xml)) { continue; }
            l = this.parsePlacemark(el[j], xml, style, schema);
            if (l) { layers.push(l); }
        }
        if (!layers.length) { return; }
        if (layers.length === 1) { return layers[0]; }
        return new L.FeatureGroup(layers);
    },

    parsePlacemark: function (place, xml, style, schema) {
        var i, j, el, options = {};
        el = place.getElementsByTagName('styleUrl');
        for (i = 0; i < el.length; i++) {
            var url = el[i].childNodes[0].nodeValue;
            for (var a in style[url]) {
                // for jshint
                if (true) {
                    options[a] = style[url][a];
                }
            }
        }
        var layers = [];
        options.attributes = [];
        if (place.getElementsByTagName('ExtendedData').length > 0) {
            var schemaId = "";
            if (place.getElementsByTagName('ExtendedData')[0].getElementsByTagName("SchemaData")[0].attributes["schemaUrl"]) {
                schemaId = place.getElementsByTagName('ExtendedData')[0].getElementsByTagName("SchemaData")[0].attributes["schemaUrl"].value;
                schemaId = schemaId.substr(1);
            }

            var simpleDatas = place.getElementsByTagName('ExtendedData')[0].getElementsByTagName('SimpleData');
            for (var nn = 0; nn < simpleDatas.length; nn++) {
                var ooo = "";
                options.attributes.push({
                    key: simpleDatas[nn].attributes["name"].value,
                    value: simpleDatas[nn].textContent,
                    type: (schemaId && schema[schemaId] ? schema[schemaId][simpleDatas[nn].attributes["name"].value] : "string")
                });
            }
        }

        //inline style
        if (place.getElementsByTagName('Style').length > 0) {
            var color = place.getElementsByTagName('Style')[0].getElementsByTagName('color');
            if (color.length > 0) {
                var value = color[0].textContent;
                options.opacity = parseInt(value.substring(0, 2), 16) / 255.0;
                options.color = "#" + value.substring(6, 8) + value.substring(4, 6) + value.substring(2, 4);
            }
        }


        var parse = ['LineString', 'Polygon', 'Point'];
        for (j in parse) {
            // for jshint
            if (true) {
                var tag = parse[j];
                el = place.getElementsByTagName(tag);
                for (i = 0; i < el.length; i++) {
                    var l = this["parse" + tag](el[i], xml, options);
                    if (l) { layers.push(l); }
                }
            }
        }

        if (!layers.length) {
            return;
        }
        var layer = layers[0];
        if (layers.length > 1) {
            layer = new L.FeatureGroup(layers);
        }

        var name, descr = "";
        el = place.getElementsByTagName('name');
        if (el.length) {
            name = el[0].childNodes[0].nodeValue;
        }
        el = place.getElementsByTagName('description');
        for (i = 0; i < el.length; i++) {
            for (j = 0; j < el[i].childNodes.length; j++) {
                descr = descr + el[i].childNodes[j].nodeValue;
            }
        }

        if (name) {
            //layer.bindPopup("<h2>" + name + "</h2>" + descr);
        }

        return layer;
    },

    parseCoords: function (xml) {
        var el = xml.getElementsByTagName('coordinates');
        return this._read_coords(el[0]);
    },

    parseLineString: function (line, xml, options) {
        var coords = this.parseCoords(line);
        if (!coords.length) { return; }
        return new L.Polyline(coords, options);
    },

    parsePoint: function (line, xml, options) {
        var el = line.getElementsByTagName('coordinates');
        if (!el.length) {
            return;
        }
        var ll = el[0].childNodes[0].nodeValue.split(',');
        return new L.KMLMarker(new L.LatLng(ll[1], ll[0]), options);
    },

    parsePolygon: function (line, xml, options) {
        var el, polys = [], inner = [], i, coords;
        el = line.getElementsByTagName('outerBoundaryIs');
        for (i = 0; i < el.length; i++) {
            coords = this.parseCoords(el[i]);
            if (coords) {
                polys.push(coords);
            }
        }
        el = line.getElementsByTagName('innerBoundaryIs');
        for (i = 0; i < el.length; i++) {
            coords = this.parseCoords(el[i]);
            if (coords) {
                inner.push(coords);
            }
        }
        if (!polys.length) {
            return;
        }
        if (options.fillColor) {
            options.fill = true;
        }
        if (polys.length === 1) {
            return new L.Polygon(polys.concat(inner), options);
        }
        return new L.MultiPolygon(polys, options);
    },

    getLatLngs: function (xml) {
        var el = xml.getElementsByTagName('coordinates');
        var coords = [];
        for (var j = 0; j < el.length; j++) {
            // text might span many childnodes
            coords = coords.concat(this._read_coords(el[j]));
        }
        return coords;
    },

    _read_coords: function (el) {
        var text = "", coords = [], i;
        for (i = 0; i < el.childNodes.length; i++) {
            text = text + el.childNodes[i].nodeValue;
        }
        text = text.split(/[\s\n]+/);
        for (i = 0; i < text.length; i++) {
            var ll = text[i].split(',');
            if (ll.length < 2) {
                continue;
            }
            coords.push(new L.LatLng(ll[1], ll[0]));
        }
        return coords;
    }

});

L.KMLIcon = L.Icon.extend({

    createIcon: function () {
        var img = this._createIcon('icon');
        img.onload = function () {
            var i = new Image();
            i.src = this.src;
            this.style.width = i.width + 'px';
            this.style.height = i.height + 'px';

            if (this.anchorType.x === 'UNITS_FRACTION' || this.anchorType.x === 'fraction') {
                img.style.marginLeft = (-this.anchor.x * i.width) + 'px';
            }
            if (this.anchorType.y === 'UNITS_FRACTION' || this.anchorType.x === 'fraction') {
                img.style.marginTop = (-(1 - this.anchor.y) * i.height) + 'px';
            }
            this.style.display = "";
        };
        return img;
    },

    _setIconStyles: function (img, name) {
        L.Icon.prototype._setIconStyles.apply(this, [img, name])
        // save anchor information to the image
        img.anchor = this.options.iconAnchorRef;
        img.anchorType = this.options.iconAnchorType;
    }
});


L.KMLMarker = L.Marker.extend({
    options: {
        icon: new L.KMLIcon.Default()
    }
});

